#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "sikle.h"

#define next(lex) (lex->p++)
#define inclinenumber(lex) (lex->line++)
#define check_char(lex, ch) ((*lex->p) == ch)

static void scan_comment(Lexer* lex)
{
  inclinenumber(lex);
  while(!check_char(lex, '\n')) 
    next(lex);
  next(lex);
  if(check_char(lex, '\r')) 
    next(lex);
}

static void scan_string(Lexer* lex, Token* tok)
{
  NString nstring;
  char buff[100];
  int i = 0;

  while(!check_char(lex,'"')) {
    if(check_char(lex, '\\')) {
      char c;
      next(lex);

      switch(*lex->p) {
      case 'n':
	c = '\n';
	break;
      case 't':
	c = '\t';
	break;
      case '"':
	c = '\"';
	break;
      case '\\':
	c = '\\';
	break;
      default:
	break;
      }
      
      buff[i++] = c;
    }
    else
      buff[i++] = *lex->p;
    next(lex);
  }
  
  next(lex);
  
  nstring = NString_new(buff, i);
  tok->type = TOK_STRING;
  tok->v.string = nstring;
}

static void scan_word(Lexer* lex, Token* tok) 
{
  char* begin = lex->p;
  char* end = NULL;
  NString nstring;

  while(!check_char(lex, ']') && 
	!check_char(lex, '[') && 
	!check_char(lex, '"') && 
	!check_char(lex, ':') && 
	isgraph(*lex->p)) 
    next(lex);

  end = lex->p;
  
  nstring = NString_new(begin, end - begin);
  tok->type = TOK_WORD;
  tok->v.string = nstring;
}

static void scan_integer(Lexer* lex, Token* tok) 
{
  char* begin;
  char* end;
  char buff[20];

  begin = lex->p;
  while(isdigit(*lex->p)) next(lex);
  end = lex->p;

  strncpy(buff, begin, end - begin);

  buff[end - begin] = 0;

  tok->v.integer = atoi(buff);
  tok->type = TOK_INTEGER;

  if(!isspace(*lex->p) && !check_char(lex, ']') && !check_char(lex, ':')) {
    log_msg(LOG_WARNING, "no space between integer and word.", tok);
  }
}

static int is_word_start(char c) 
{
  return (!isdigit(c)) && isgraph(c);
}

static int is_integer_start(char c) 
{
  return isdigit(c);
}

#define save_context(lex, tok)			\
  tok->context = lex->p;			\
  tok->line = lex->line

static void scan(Lexer* lex, Token* tok)
{
  for(;;) {
    switch(*lex->p) {
    case '\n':
      inclinenumber(lex);
      next(lex);
      save_context(lex, tok);
      continue;
    case '\r':
      next(lex);
      continue;
    case 0:
      tok->type = TOK_EOS;
      return;
    case ':':
      tok->type = TOK_COLON;
      next(lex);
      return;
    case '"':
      next(lex);
      save_context(lex, tok);
      scan_string(lex, tok);
      return;
    case '[':
      tok->type = TOK_LBRACKET;
      next(lex);
      return;
    case ']':
      tok->type = TOK_RBRACKET;
      next(lex);
      return;
    case ';':
      next(lex);
      scan_comment(lex);
      continue;
    default:
      if(isspace(*lex->p)) {
	next(lex);
	continue;
      }
      if(is_integer_start(*lex->p)) {
	scan_integer(lex, tok);
      }
      else if(is_word_start(*lex->p)) {
	scan_word(lex, tok);
      }
      else {
	next(lex);
	continue;
      }
      return;
    }
  }
}

void next_token(Lexer* lex) 
{
  if(lex->next.type != TOK_EOS) {
    lex->token = lex->next;
    lex->next.type = TOK_EOS;
  }
  else {
    scan(lex, &lex->token);
  }
}

void peek(Lexer* lex) 
{
  if(lex->next.type == TOK_EOS) {
    scan(lex, &lex->next);
  }
}

void Lexer_init(Lexer* lexer, char* filename) 
{
  char* buffer = NULL;
  FILE* file = NULL;
  int filesize = 0;
  int copysize = 0;

  file = fopen(filename, "rb");

  if(file == NULL){
    fprintf(stderr, "Can`t open file %s", filename);
    exit(1);
  }

  fseek(file, 0, 2);
  filesize = ftell(file);
  rewind(file);

  buffer = (char*)malloc(filesize + 1);

  if(buffer == NULL){
    fprintf(stderr, "No more memory");
    exit(2);
  }

  copysize = fread(buffer, 1, filesize, file);
  buffer[filesize] = 0;

  if(copysize != filesize){
    fprintf(stderr, "Not read the whole file");
    exit(3);
  }

  lexer->p = lexer->base = buffer;
  lexer->line = 1;
  lexer->token.type = TOK_EOS;
  lexer->next.type = TOK_EOS;
}

#if 0

const char* const token_string[] = {
  "integer",
  ":",
  "string",
  "word",
  "[",
  "]",
  "eos"
};

void test_lexer(Lexer* lex) {
  next_token(lex);
  
  while(tok_type(lex) != TOK_EOS) {
    switch(tok_type(lex)) {
    case TOK_INTEGER:
      printf("line %d:%s\t=> %d\n", 
	     lex->line, 
	     token_string[tok_type(lex)],
	     lex->token.v.integer);
      break;
    case TOK_STRING:
    case TOK_WORD:
      printf("line %d:%s\t=> %s\n", 
	     lex->line, 
	     token_string[tok_type(lex)],
	     lex->token.v.string.data);
      break;
    default:
      printf("line %d:%s\n", 
	     lex->line, 
	     token_string[tok_type(lex)]);
    }

    next_token(lex);
  }
}

int main(int argc, char** argv)
{
  Lexer lex;

  if(argc != 2){
    printf("Usage: sikle filename\n");
    exit(1);
  }

  Lexer_init(&lex, argv[1]);
  test_lexer(&lex);

  return 0;
}

#endif
